/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.leinardi.ohos.speeddial;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorProperty;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.*;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.components.element.StateElement;
import ohos.agp.render.Canvas;
import ohos.agp.render.MaskFilter;
import ohos.agp.render.Paint;
import ohos.agp.utils.Circle;
import ohos.agp.utils.Color;
import ohos.app.Context;

public class FloatingActionButton extends StackLayout implements Component.DrawTask {
    // Size is 56vp, default value
    public static final int SIZE_NORMAL = 0;
    // Size is 40vp
    public static final int SIZE_MINI = 1;
    // Animation duration
    private static final int ANIM_DURATION = 150;

    // custom attributes
    private static final String fab_colorNormal = "fab_colorNormal";
    private static final String fab_colorPressed = "fab_colorPressed";
    private static final String fab_showShadow = "fab_showShadow";
    private static final String fab_showShadowColor = "fab_showShadowColor";
    private static final String fab_shadowXOffset = "fab_shadowXOffset";
    private static final String fab_shadowYOffset = "fab_shadowYOffset";
    private static final String fab_size = "fab_size";

    private int mFabSize = SIZE_NORMAL;

    private boolean mShowShadow;
    private int mShadowColor;
    private int mShadowXOffset = AttrHelper.vp2px(1f, getContext());
    private int mShadowYOffset = AttrHelper.vp2px(4f, getContext());

    private int mMarginLeftAndRight = (int) UiUtils.getDimensionValue(getContext(), ResourceTable.Float_fab_margin_left_and_right);
    private int mMarginTopAndBottom = (int) UiUtils.getDimensionValue(getContext(), ResourceTable.Float_fab_margin_top_and_bottom);

    private int mColorNormal;
    private int mColorPressed;

    private Component mBgComponent;
    private Image mIconImage;

    private AnimatorProperty mAnimatorIn;
    private AnimatorProperty mAnimatorOut;

    public FloatingActionButton(Context context) {
        super(context);
        initWithContext(context, null);
    }

    public FloatingActionButton(Context context, AttrSet attrSet) {
        super(context, attrSet);
        initWithContext(context, attrSet);
    }

    public FloatingActionButton(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        initWithContext(context, attrSet);
    }

    private void initWithContext(Context context, AttrSet attrSet) {
        mContext = context;
        if (attrSet != null) {
            mColorNormal = AttrUtils.getColorValueByAttr(attrSet, fab_colorNormal, new Color(UiUtils.getAccentColor(context))).getValue();
            mColorPressed = AttrUtils.getColorValueByAttr(attrSet, fab_colorPressed, new Color(getDefaultPressColor())).getValue();
            mShowShadow = AttrUtils.getBooleanValueByAttr(attrSet, fab_showShadow, false);
            mShadowColor = AttrUtils.getColorValueByAttr(attrSet, fab_showShadowColor, new Color(0x42000000)).getValue();
            mShadowXOffset = AttrUtils.getDimensionValueByAttr(attrSet, fab_shadowXOffset, mShadowXOffset);
            mShadowYOffset = AttrUtils.getDimensionValueByAttr(attrSet, fab_shadowYOffset, mShadowYOffset);
            mFabSize = AttrUtils.getIntValueByAttr(attrSet, fab_size, SIZE_NORMAL);
        } else {
            mColorNormal = 0xFFDA4336;
            mColorPressed = 0xFFE75043;
            mShowShadow = false;
            mShadowColor = 0x42000000;
            mFabSize = SIZE_NORMAL;
        }

        onMeasure();

        setClipEnabled(false);
        setClickable(true);
        addDrawTask(this, Component.DrawTask.BETWEEN_BACKGROUND_AND_CONTENT);

        // add background component
        addBackgroundComponent();
        // add icon
        addIconImage();
    }

    private void addBackgroundComponent() {
        mBgComponent = new Component(mContext);

        ComponentContainer.LayoutConfig layoutConfig = new ComponentContainer.LayoutConfig();
        layoutConfig.width = calculateMeasuredWidth();
        layoutConfig.height = calculateMeasuredHeight();
        setLayoutConfig(layoutConfig);

        addComponent(mBgComponent, layoutConfig);
        mBgComponent.setMarginsLeftAndRight(mMarginLeftAndRight, mMarginLeftAndRight);
        mBgComponent.setMarginsTopAndBottom(mMarginTopAndBottom, mMarginTopAndBottom);
    }

    private void addIconImage() {
        mIconImage = new Image(mContext);
        mIconImage.setScaleMode(Image.ScaleMode.STRETCH);
        int size = (int) UiUtils.getDimensionValue(mContext, ResourceTable.Float_fab_icon_size);
        StackLayout.LayoutConfig layoutConfig = new StackLayout.LayoutConfig(size, size);
        //layoutConfig.alignment = LayoutAlignment.VERTICAL_CENTER;
        layoutConfig.setMarginTop((calculateMeasuredHeight() - size) / 2 + mMarginTopAndBottom);
        layoutConfig.setMarginLeft((calculateMeasuredWidth() - size) / 2 + mMarginLeftAndRight);
        mIconImage.setLayoutConfig(layoutConfig);
        addComponent(mIconImage, layoutConfig);
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        // draw shadow
        if (mShowShadow) {
            Paint mShadowPaint = new Paint();
            mShadowPaint.setColor(new Color(mShadowColor));
            mShadowPaint.setAntiAlias(true);
            mShadowPaint.setMaskFilter(new MaskFilter(AttrHelper.fp2px(10f, mContext), MaskFilter.Blur.NORMAL));
            canvas.drawCircle(new Circle(getCircleSize() / 2f + mMarginLeftAndRight + mShadowXOffset, getCircleSize() / 2f
                    + mMarginTopAndBottom + mShadowYOffset, getCircleSize() / 2f), mShadowPaint);
        }

        onMeasure();
        updateBackground();
    }

    private void onMeasure() {
        if (mBgComponent != null) {
            ComponentContainer.LayoutConfig layoutConfig = mBgComponent.getLayoutConfig();
            layoutConfig.width = calculateMeasuredWidth();
            layoutConfig.height = calculateMeasuredHeight();
            mBgComponent.setLayoutConfig(layoutConfig);
        }
    }

    public int calculateMeasuredWidth() {
        return getCircleSize() + calculateShadowWidth();
    }

    public int calculateMeasuredHeight() {
        return getCircleSize() + calculateShadowHeight();
    }

    private int getCircleSize() {
        return (int) UiUtils.getDimensionValue(mContext,
                mFabSize == SIZE_NORMAL ? ResourceTable.Float_fab_size_normal :
                        ResourceTable.Float_fab_size_mini);
    }

    private int calculateShadowWidth() {
        //return hasShadow() ? getShadowX() * 2 : 0;
        return 0;
    }

    private int calculateShadowHeight() {
        //return hasShadow() ? getShadowY() * 2 : 0;
        return 0;
    }

    private int getShadowX() {
        return Math.abs(mShadowXOffset);
    }

    private int getShadowY() {
        return Math.abs(mShadowYOffset);
    }

    public void setShowShadow(boolean show) {
        if (mShowShadow != show) {
            mShowShadow = show;
            updateBackground();
        }
    }

    public boolean hasShadow() {
        return mShowShadow;
    }

    public void setButtonSize(int size) {
        if (size != SIZE_NORMAL && size != SIZE_MINI) {
            throw new IllegalArgumentException("Use @FabSize constants only!");
        }

        if (mFabSize != size) {
            mFabSize = size;
            updateBackground();
        }
    }

    public void setColorNormal(int color) {
        if (mColorNormal != color) {
            mColorNormal = color;
            // 自动更新按压颜色值
            mColorPressed = getDefaultPressColor();
            updateBackground();
        }
    }

    public void setColorNormalResId(int colorResId) {
        setColorNormal(UiUtils.getColor(getContext(), colorResId));
    }

    public int getColorNormal() {
        return mColorNormal;
    }

    public void setColorPressed(int color) {
        if (color != mColorPressed) {
            mColorPressed = color;
            updateBackground();
        }
    }

    public void setColorPressedResId(int colorResId) {
        setColorPressed(UiUtils.getColor(getContext(), colorResId));
    }

    public int getColorPressed() {
        return mColorPressed;
    }

    public void setImageElement(Element element) {
        if (mIconImage != null && element != null) {
            mIconImage.setImageElement(element);
        }
    }

    public Image getIconImage() {
        return mIconImage;
    }

    public void show(OnVisibilityChangedListener listener) {
        if (getVisibility() != VISIBLE) {
            setVisibility(VISIBLE);
            if (mAnimatorIn == null) {
                mAnimatorIn = new AnimatorProperty(this);
                mAnimatorIn.setDuration(ANIM_DURATION);
                mAnimatorIn.setCurveType(Animator.CurveType.DECELERATE);
                mAnimatorIn.setStateChangedListener(new AnimStateChangedListener() {
                    @Override
                    public void onStart(Animator animator) {
                        if (listener != null) {
                            listener.onShown(FloatingActionButton.this);
                        }
                    }
                });
            }
            mAnimatorIn.scaleXFrom(getScaleX()).scaleX(1f);
            mAnimatorIn.scaleYFrom(getScaleY()).scaleY(1f);
            if (mAnimatorOut != null && mAnimatorOut.isRunning()) {
                mAnimatorOut.stop();
            }
            if (!mAnimatorIn.isRunning()) {
                mAnimatorIn.start();
            }
        }
    }

    public void hide(OnVisibilityChangedListener listener) {
        if (getVisibility() != HIDE) {
            if (mAnimatorOut == null) {
                mAnimatorOut = new AnimatorProperty(this);
                mAnimatorOut.setDuration(ANIM_DURATION);
                mAnimatorOut.setCurveType(Animator.CurveType.ACCELERATE);
                mAnimatorOut.setStateChangedListener(new AnimStateChangedListener() {
                    @Override
                    public void onEnd(Animator animator) {
                        setVisibility(HIDE);
                        if (listener != null) {
                            listener.onHidden(FloatingActionButton.this);
                        }
                    }
                });
            }
            mAnimatorOut.scaleXFrom(getScaleX()).scaleX(0f);
            mAnimatorOut.scaleYFrom(getScaleY()).scaleY(0f);
            if (mAnimatorIn != null && mAnimatorIn.isRunning()) {
                mAnimatorIn.stop();
            }
            if (!mAnimatorOut.isRunning()) {
                mAnimatorOut.start();
            }
        }
    }

    public void updateFabMargin(int orientation) {
        if (orientation == HORIZONTAL) {
            mMarginLeftAndRight = (int) UiUtils.getDimensionValue(getContext(), ResourceTable.Float_fab_margin_left_and_right);
            mMarginTopAndBottom = (int) UiUtils.getDimensionValue(getContext(), ResourceTable.Float_fab_margin_top_and_bottom);
        } else {
            mMarginLeftAndRight = (int) UiUtils.getDimensionValue(getContext(), ResourceTable.Float_fab_margin_top_and_bottom);
            mMarginTopAndBottom = (int) UiUtils.getDimensionValue(getContext(), ResourceTable.Float_fab_margin_left_and_right);
        }
        mBgComponent.setMarginsLeftAndRight(mMarginLeftAndRight, mMarginLeftAndRight);
        mBgComponent.setMarginsTopAndBottom(mMarginTopAndBottom, mMarginTopAndBottom);

        updateFabIconMargin();
    }

    private void updateFabIconMargin() {
        LayoutConfig layoutConfig = (StackLayout.LayoutConfig) mIconImage.getLayoutConfig();
        int size = (int) UiUtils.getDimensionValue(mContext, ResourceTable.Float_fab_icon_size);
        layoutConfig.setMarginTop((calculateMeasuredHeight() - size) / 2 + mMarginTopAndBottom);
        layoutConfig.setMarginLeft((calculateMeasuredWidth() - size) / 2 + mMarginLeftAndRight);
        mIconImage.setLayoutConfig(layoutConfig);
    }

    private void updateBackground() {
        if (mBgComponent != null) {
            mBgComponent.setBackground(createFillDrawable());
        }
    }

    private Element createFillDrawable() {
        StateElement drawable = new StateElement();
        drawable.addState(new int[]{ComponentState.COMPONENT_STATE_PRESSED}, createCircleDrawable(mColorPressed));
        drawable.addState(new int[]{}, createCircleDrawable(mColorNormal));

        return drawable;
    }

    private Element createCircleDrawable(int color) {
        CircleDrawable shapeDrawable = new CircleDrawable(ShapeElement.OVAL);
        shapeDrawable.setRgbColor(RgbColor.fromArgbInt(color));
        return shapeDrawable;
    }

    private int getDefaultPressColor() {
        RgbColor rgbColor = RgbColor.fromArgbInt(mColorNormal);
        return Color.argb((int) (rgbColor.getAlpha() * 0.7), rgbColor.getRed(), rgbColor.getGreen(), rgbColor.getBlue());
    }

    @Override
    public void setClickedListener(ClickedListener listener) {
        if (mBgComponent != null) {
            mBgComponent.setClickedListener(listener);
        }
    }

    private class CircleDrawable extends ShapeElement {
        private CircleDrawable(int shape) {
            super();
            setShape(shape);
        }

        @Override
        public void drawToCanvas(Canvas canvas) {
            setBounds(0, 0, calculateMeasuredWidth(), calculateMeasuredHeight());
            super.drawToCanvas(canvas);
        }
    }

    public static class OnVisibilityChangedListener {
        public void onShown(FloatingActionButton fab) {
        }

        public void onHidden(FloatingActionButton fab) {
        }
    }
}
